package com.analyize;

import java.util.ArrayList;

/**
 * 线程安全分析
 * 线程安全主要是面对与线程并发访问同一个资源时，会出现
 * 那么资源，抽象过来，可以理解是变量，那么变量，有几种，成员变量，静态变量，局部变量
 * 成员变量和静态变量是否线程安全？
 * 如果它们没有共享，则线程安全
 * 如果它们被共享了，根据它们的状态是否能够改变，又分两种情况
 * 如果只有读操作，则线程安全
 * 如果有读写操作，那么，这段代码就是临界区，需要考虑线程安全
 * 局部变量是否线程安全？
 * 局部变量是线程安全的
 * But!局部变量引用的对象，未必
 * 如果该对象没有逃离方法的作用范围，那么它是线程安全的
 * 如果该对象逃离方法的作用范围，需要考虑线程安全
 */
public class ThreadSafeAnalyize {
    /**
     * 局部变量线程安全分析
     * 这种情况下，每个线程调用test1()方法中的局部变量i，
     * 会在每个线程内存中被创建多份，因此不存在共享
     * 其实从JVM角度来说，虚拟机栈，是每个线程私有的
     * 而栈中有一个一个的栈帧
     * 栈帧中存放有几类数据
     * 1、局部变量表 存放方法参数和方法内部定义的局部变量 在方法执行时，JVM 会使用局部变量表来完成参数值到参数变量列表的传递过程。
     *      对于不同数据类型存放时也有些区别
     *      基本数据类型：值直接存放于局部变量表
     *      引用类型：对于对象引用(referenec)，局部变量表中存的是对象在堆内存中的地址
     *      返回地址类型：用于指向一条字节码指令的地址
     *2、操作数栈
     *      临时的数据存储区域，用于方法执行过程中的计算和操作数的存储
     *      方法执行时，根据字节码指令，将操作数压入操作数栈，进行相应运算，结果也存在数栈中
     *3、动态链接
     *       每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用，用于支持方法调用过程中的动态链接
     *       Java程序运行时，需要将符号引用转为直接引用，也就是在运行时常量池中查找方法的实际地址，这个过程就需要
     *       动态链接来完成。这样，方法调用时，就可以根据动态链接找到方法的具体实现并执行
     *4、方法返回地址
     *      当方法执行完成后，需要返回调用它的方法继续执行，方法返回地址就是用于记录方法执行完毕后应该返回的位置。
     *      方法返回地址可能是调用该方法的指令的下一条指令的地址，也可能是根据方法的调用方式和字节码指令的具体情况来确定的其他地址。
     *      当方法正常返回时，会根据这个地址返回到调用者的代码中继续执行；
     *      如果方法是通过抛出异常结束的，则不会使用这个返回地址，而是由异常处理机制来决定程序的流向。
     * 所以说如此，对于每个方法的局部变量，是每个线程私有的，不会被共享，因此，线程安全
     * 但是局部变量所引用的对象则未必，为此，我们可以先看一个成员变量的例子，如test2
     *
     */
    /**
     * 局部变量线程安全分析
     */
    public static void test1() {
        int i = 0;
        i++;
    }

    /**
     * 成员变量线程安全分析
     */
    static ArrayList<String> list = new ArrayList<>();

    public static void test2(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            //临界区，产生竞态条件
            method1();

            method2();
        }
    }

    private static void method1() {
        list.add("1");
    }

    private static void method2() {
        list.remove(0);
    }

    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;

    // 其中一种情况是，线程2还没add，线程1remove，就会抛出异常
    public static void main(String[] args) {
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                //test2(LOOP_NUMBER);
                test3(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }

    //那么要解决这个问题，我们可以对上述的方法进行修改
    //将成员变量修改为方法局部变量，就不会出现这个问题

    /**
     * TODO 那么，问题来了，这里是不是可以带来对于方法修饰符的思考
     * 如果，我把修饰符改成了public
     * 那么是不是意味着这个类的method2和method3方法可以被任意地方任意调用
     * 那么一旦出现这种情况，线程就不会安全
     * 但是如果改为public，使用时依旧只有test3这个方法，那么当然，是不会出现线程安全的
     * 最怕的就是，这里使用test3方法
     * 别的地方又单独调用method2，method3方法
     * 那是有概率出现线程安全问题的
     *
     * @param loopNumber
     */
    public static final void test3(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }

    private static void method2(ArrayList<String> list) {
        list.add("1");
    }

    private static void method3(ArrayList<String> list) {
        list.remove(0);
    }
}

